/*
 * Copyright (C) 2012-2013 Michael L.R. Marques
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 * 
 * Contact: michaellrmarques@gmail.com
 */

package com.jm.commons.lookup;

import com.jm.commons.utils.Constants;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Pattern;

/**
 * Lookup API for reflection type operations
 * 
 * @created Nov 29, 2012
 * @author Michael L.R. Marques
 */
public class Lookup {
    
    /**
     * Searches a Jar File
     * Returns a collection of class names that inherit from the passed interface
     * 
     * @param jarFile
     * @param interfaceClass
     * @return
     * @throws FileNotFoundException 
     */
    public static Class[] byInterface(String jarFile, String interfaceClass) throws FileNotFoundException {
        return byInterface(new File(jarFile), interfaceClass);
    }
    
    /**
     * Searches a Jar File
     * Returns a collection of class names that inherit from the passed interface
     * 
     * @param jarFile
     * @param interfaceClass
     * @return
     * @throws FileNotFoundException 
     */
    public static Class[] byInterface(File jarFile, String interfaceClass) throws FileNotFoundException {
        // Check if the Jar file exists
        if (!jarFile.exists()) {
            throw new FileNotFoundException(jarFile.getAbsolutePath());
        }
        // Initializie the classes array list
        List<Class> classes = new ArrayList();
        // Catch the io exception 
        try {
            // Create enumeration of the jar entries
            Enumeration<JarEntry> entries = new JarFile(jarFile).entries();
            // Loop through the entries
            while (entries.hasMoreElements()) {
                // Next entry
                JarEntry entry = entries.nextElement();
                // Use regular expression to check if the entry is a class
                if (!entry.getName().contains("$") &&
                        Pattern.matches(Constants.CLASS_REGEX_PATTERN, entry.getName())) {
                    // Catch an exception incase the URL is malformed
                    try {
                        // Create the jar files class loader
                        try (URLClassLoader ucl = new URLClassLoader(new URL[] {new URL("jar:file:" + jarFile.getAbsolutePath() + "!/")})) {
                            // Catch an exception incase the class is not found
                            try {
                                // Load the class
                                Class loadClass = ucl.loadClass(entry.getName().replaceAll("/", ".").replace(".class", ""));
                                // Iterate through the classes interfaces
                                for (Class iClass : loadClass.getInterfaces()) {
                                    // If the interface matches the passes interface class
                                    if (iClass.getName().equals(interfaceClass)) {
                                        // Add it to the Class collection and break from the interface iteration
                                        classes.add(loadClass);
                                        break;
                                    }
                                }
                            } catch (ClassNotFoundException | NoClassDefFoundError e) {
                                System.out.println("Class not found: " + entry.getName());
                            }
                        }
                    } catch (MalformedURLException murle) {
                        System.out.println(murle.getMessage());
                    }
                }
            }
        } catch (IOException ioe) {
            System.out.println(ioe.getMessage());
        }
        // Convert the list to a class array
        return classes.toArray(new Class[] {});
    }
    
    /**
     * Searches a Jar File
     * Returns a collection of classes that inherit from the passed interface
     * 
     * 
     * @param jarFile
     * @param interfaceClass
     * @return
     * @throws FileNotFoundException 
     */
    public static Class[] byInterface(String jarFile, Class interfaceClass) throws FileNotFoundException {
        return byInterface(new File(jarFile), interfaceClass);
    }
    
    /**
     * Searches a Jar File
     * Returns a collection of classes that inherit from the passed interface
     * 
     * @param jarFile
     * @param interfaceClass
     * @return
     * @throws FileNotFoundException 
     */
    public static Class[] byInterface(File jarFile, Class interfaceClass) throws FileNotFoundException {
        // Check if the Jar file exists
        if (!jarFile.exists()) {
            throw new FileNotFoundException(jarFile.getAbsolutePath());
        }
        // Initializie the classes array list
        List<Class> classes = new ArrayList();
        // Catch the io exception 
        try {
            // Create enumeration of the jar entries
            Enumeration<JarEntry> entries = new JarFile(jarFile).entries();
            // Loop through the entries
            while (entries.hasMoreElements()) {
                // Next entry
                JarEntry entry = entries.nextElement();
                // Use regular expression to check if the entry is a class
                if (!entry.getName().contains("$") &&
                        Pattern.matches(Constants.CLASS_REGEX_PATTERN, entry.getName())) {
                    // Catch an exception incase the URL is malformed
                    try {
                        // Create the jar files class loader
                        try (URLClassLoader ucl = new URLClassLoader(new URL[] {new URL("jar:file:" + jarFile.getAbsolutePath() + "!/")})) {
                            // Catch an exception incase the class is not found
                            try {
                                // Load the class
                                Class loadClass = ucl.loadClass(entry.getName().replaceAll("/", ".").replace(".class", ""));
                                // Iterate through the classes interfaces
                                for (Class iClass : loadClass.getInterfaces()) {
                                    // If the interface matches the passed interface class
                                    if (iClass.equals(interfaceClass)) {
                                        // Add it to the Class collection and break from the interface iteration
                                        classes.add(loadClass);
                                        break;
                                    }
                                }
                            } catch (ClassNotFoundException | NoClassDefFoundError e) {
                                System.out.println("Class not found: " + entry.getName());
                            }
                        }
                    } catch (MalformedURLException murle) {
                        System.out.println(murle.getMessage());
                    }
                }
            }
        } catch (IOException ioe) {
            System.out.println(ioe.getMessage());
        }
        // Convert the list to a class array
        return classes.toArray(new Class[] {});
    }

}
